Namespaces  >  Custom Attributes  >  Garbage Collection

Custom Attributes

.NET Framework assemblies are self-describing entities. They contain intermediate code that is compiled to native machine instructions when the assembly is loaded. More than that, assemblies contain a wealth of information about that code. The compiler emits this descriptive information, or metadata, into the assembly as it processes the source code. In traditional programming environments, there is no way to access metadata once your code is compiled; the information is lost during the compilation process. On the .NET Framework, however, you have the ability to access metadata using runtime reflection services.

 

Even so, the compiler can only emit metadata for language keywords and constructs that it knows about. The .NET Framework gives you the ability to extend the metadata emitted by the compiler with your own descriptive attributes. These customized attributes are somewhat analogous to language keywords, and are stored with the other metadata in the assembly. This means you can access them by using standard reflection services provided by the CLR.

Using Custom Attributes

Creating a custom attribute is the same as declaring a class. The custom attribute class has a constructor, and properties to set and retrieve state data. Custom attributes must inherit from TCustomAttribute. The following code declares a custom attribute with a constructor and two properties:

 

      type

            TCustomCodeAttribute = class(TCustomAttribute)

                  private

                        Fprop1 : integer;

                        Fprop2 : integer;

                        aVal : integer;

                        procedure Setprop1(p1 : integer);

                        procedure Setprop2(p2 : integer);

                  public

                        constructor Create(const myVal : integer);

                        property prop1 : integer read Fprop1 write Setprop1;

                        property prop2 : integer read Fprop2 write Setprop2;

            end;

 

The constructor would be implemented as usual:

 

      constructor TCustomCodeAttribute.Create(const myVal : integer);

      begin

            aVal := myVal;

      end;

 

Place the custom attribute directly before the class to which the attribute applies. Note that Delphi for Microsoftâ .NET supports the use of named properties in the initialization. These can be the names of properties, or of public fields of the custom attribute class. Named properties are listed after all of the parameters required by the constructor.

 

      [TCustomCodeAttribute(1024, prop1=512, prop2=128)]

      TMyClass = class(TObject)

      …

      end;

 

The first parameter, 1024, is the value required by the constructor. The second two parameters are the properties defined in the custom attribute.

 

Custom attributes applied to types can be detected at runtime with the GetCustomAttributes method of the System.Type class. The following Delphi code demonstrates how to query for custom attributes at runtime.

 

var

F: TMyClass;          // TMyClass declared above

T: System.Type;

A: array of TObject;  // Will hold custom attributes

I: Integer;

begin

F := TMyClass.Create;

T := F.GetType;

A := T.GetCustomAttributes(True);

 

// Write the type name, and then loop over custom

// attributes returned from the call to

// System.Type.GetCustomAttributes.

Writeln(T.FullName);

for I := Low(A) to High(A) do

Writeln(A[I].GetType.FullName);

end.

 

Using the DllIimport Custom Attribute

You can call unmanaged Win32 APIs (and other unmanaged code) by prefixing the function declaration with the DllImport custom attribute. This attribute resides in the System.Runtime.InteropServices namespace, as shown below:

 

      Program HellowWorld2;

 

      // Don't forget to include the InteropServices unit when using the DllImport attribute.

      uses System.Runtime.InteropServices;

 

      [DllImport('user32.dll')]

      function MessageBeep(uType : LongWord) : Boolean; external;

 

      begin

            MessageBeep(LongWord(-1));

      end.

 

Note the external keyword is still required, to replace the block in the function declaration. All other attributes, such as the calling convention, can be passed through the DllImport custom attribute.

Custom Attributes and the GUID Specifier

Delphi syntax dictates that the GUID (if present) must immediately follow the declaration of an interface. Since the GUID syntax is similar to that of custom attributes, the compiler must be made to know the difference between a custom attribute - which applies to the next declaration - and a GUID specifier, which applies to the previous declaration. Without this special case, the compiler would try to apply an attribute to the first member of the interface.

 

When the compiler sees an interface declaration, the next square bracket construct found is assumed to be that of a GUID specifier for the interface. The GUID must be in the traditional Delphi form:

 

      ['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']

 

Alternatively, you can use the Guid custom attribute of the .NET Framework (System.Runtime.InteropServices.GuidAttribute). If you choose this method, then you introduce the attribute before the interface, as with any other custom attribute.

 

The effect in either case is the same: the GUID is emitted into the metadata for the interface type. Note that GUIDs are not required for interfaces in the .NET Framework. They are only used for COM interoperability.

The Future

For the preview release of the Delphi for .NET compiler, custom attributes can only be applied to types. They cannot yet be applied to methods, or method parameters, or function results. The custom attribute scope resolution prefixes (assembly, module, type, event, property, field, return, method, and param) are not implemented. Nor is the AttributeUsageTargets enumeration. For the preview release the attribute usage target is assumed to be All.


Top  |  Contents  |  Copyrights  |  Borland Developer Network  |  Borland Home